﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace FourPartHarmony3 {

    class MidiOutput {
        public List<Tuple<int, string>> GetDeviceList() {
            List<Tuple<int, string>> result = new List<Tuple<int, string>>();

            for (int i = 0; i < (int)midiOutGetNumDevs(); ++i) {
                var caps = new MIDIOUTCAPS();
                if (midiOutGetDevCaps(i, ref caps, (UInt32)Marshal.SizeOf(caps)) == MMSYSERR_NOERROR) {
                    result.Add(new Tuple<int,string>(i, caps.szPname));
                }
            }
            return result;
        }

        public bool Open(int deviceId) {
            mDeviceHandle = 0;
            var rv = midiOutOpen(ref mDeviceHandle, deviceId, null, 0, 0);
            if (MMSYSERR_NOERROR != rv) {
                Console.WriteLine("E: MidiOutput.Open(deviceId={0}) failed", deviceId);
                return false;
            }

            mUseDeviceId = deviceId;
            // Console.WriteLine("D: MidiOutput.Open(deviceId={0}) deviceHandle={1}", deviceId, mDeviceHandle);
            return true;
        }

        public void Close() {
            if (mUseDeviceId < 0) {
                return;
            }

            // Console.WriteLine("D: MidiOutput.Close(deviceHandle={0})", mDeviceHandle);
            midiOutClose(ref mDeviceHandle);
            mUseDeviceId = -1;
        }

        public int SendShortMsg(int command, int ch, int data1, int data2) {
            uint msg = (uint)command | (uint)ch;
            msg |= (uint)data1 << SHORT_MSG_SHIFT;
            msg |= (uint)data2 << SHORT_MSG_SHIFT * 2;

            return midiOutShortMsg(mDeviceHandle, msg); 
        }

        public bool SendReset() {
            if (mUseDeviceId < 0) {
                return false;
            }

            midiOutReset(mDeviceHandle);
            return true;
        }

        public int GetUseDeviceId() {
            return mUseDeviceId;
        }

        public const int COMMAND_NOTE_ON  = 0x90;
        public const int COMMAND_NOTE_OFF = 0x80;
        public const int COMMAND_RESET    = 0xff;

        private int mUseDeviceId = -1;
        private int mDeviceHandle;

        internal const int  SHORT_MSG_SHIFT = 8;
        internal const int MAXPNAMELEN = 32;
        internal const uint MMSYSERR_NOERROR = 0;

        [StructLayout(LayoutKind.Sequential)]
        internal struct MIDIOUTCAPS {
            public UInt16 wMid;
            public UInt16 wPid;
            public UInt32 vDriverVersion;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAXPNAMELEN)]
            public string szPname;
            public UInt16 wTechnology;
            public UInt16 wVoices;
            public UInt16 wNotes;
            public UInt16 wChannelMask;
            public UInt32 dwSupport;
        }

        [DllImport("winmm.dll")]
        internal static extern uint midiOutGetNumDevs();

        [DllImport("winmm.dll")]
        internal static extern uint midiOutGetDevCaps(int uDeviceID, ref MIDIOUTCAPS lpMidiOutCaps, uint cbMidiOutCaps);

        internal delegate void MidiCallback(int handle, int msg, int instance, int param1, int param2);
        
        [DllImport("winmm.dll")]
        internal static extern int midiOutOpen(ref int handle, int deviceID, MidiCallback proc, int instance, int flags);

        [DllImport("winmm.dll")]
        internal static extern int midiOutShortMsg(int handle, uint message);

        [DllImport("winmm.dll")]
        static extern uint midiOutClose(ref int handle);

        [DllImport("winmm.dll")]
        internal static extern int midiOutReset(int handle);
    }
}
